#include "messageshowarea.h"
#include <QScrollBar>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QFontMetrics>
#include <QPainter>
#include <QPainterPath>
#include <QFileDialog>
#include <QMenu>
#include <QTimer>
#include <QDebug>
#include "model/datacenter.h"
#include "mainwidget.h"
#include "debug.h"
#include "userinfowidget.h"
#include "toast.h"
#include "soundrecorder.h"

using namespace model;

MessageShowArea::MessageShowArea()
    //1.初始化基本属性
{
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    this->setWidgetResizable(true);

    //设置滚动条样式
    this->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 8px; background-color: rgb(240, 240, 240);}");
    this->horizontalScrollBar()->setStyleSheet("QScorllBar:horizontal { height: 0;}");
    this->setStyleSheet("QScrollArea {border: none;}");

    //2.创建container 这样的Widget, 作为包含内部元素的容器
    container = new QWidget();
    this->setWidget(container);

    //3. 给container 添加布局管理器
    QVBoxLayout* layout = new QVBoxLayout();
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);
    container->setLayout(layout);

    //添加构造测试数据

#if TEST_UI
    UserInfo userInfo;
    userInfo.nickname = "誓约";
    userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
    userInfo.userId = QString::number(1000);
    userInfo.description = "好好学习天天向上";
    userInfo.phone = "18123123124";

    Message message = Message::makeMessage(TEXT_TYPE, " ", userInfo, (QString("这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息")).toUtf8(), " ");
    this->addMessage(false, message);
    for (int i = 0; i < 30; ++i)
    {
        UserInfo userInfo;
        userInfo.nickname = "誓约" +QString::number(i + 1000);
        userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
        userInfo.userId = QString::number(i);
        userInfo.phone = "18123456";
        userInfo.description = "好好学习天天向上";


        Message message = Message::makeMessage(TEXT_TYPE, " ", userInfo, (QString("这是一条测试消息") + QString::number(i)).toUtf8(), " ");
        this->addMessage(true, message);
    }

#endif

}

void MessageShowArea::addMessage(bool isLeft, const Message &message)
{
    //构造 MessageItem, 添加到布局管理器中.
    MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
    container->layout()->addWidget(messageItem);
}

void MessageShowArea::addFrontMessage(bool isLeft, const Message &message)
{
    MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);

    //container->layout()返回的是QLayout对象的指针,Qlayout中没有对应的头插方法,需要将其转换为有insertWidget方法的QVBoxLayout
    QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(container->layout());

    layout->insertWidget(0, messageItem);
}

void MessageShowArea::clear()
{
    //遍历布局管理器删除其中元素
    QLayout* layout = container->layout();
    for (int i = layout->count(); i >=0; --i)
    {
        QLayoutItem* item = layout->takeAt(i);
        if (item != nullptr && item->widget() != nullptr)
        {
            delete item->widget();
        }
    }
}

void MessageShowArea::scrollToEnd()
{
    // 实现思路:
    // 拿到滚动区域的滚动条(垂直滚动条)
    // 获取到滚动条的最大值
    //根据最大值设置, 设置滚动条的滚动位置
    //为了使滚动效果更佳,能够在界面绘制好之后进行滚动条的设置
    //这里的滚动操作,加上个延时

    QTimer* timer = new  QTimer();
    connect(timer, &QTimer::timeout, this, [=](){
        //获取到垂直滚动条的最大值
        int maxValue = this->verticalScrollBar()->maximum();
        //设置滚动条的滚动位置
        this->verticalScrollBar()->setValue(maxValue);
        timer->stop();
        timer->deleteLater();

    });
    //毫秒
    timer->start(500);

}

//////////////////////////////
///表示一个消息元素
/////////////////////////////
MessageItem::MessageItem(bool isLeft)
    :isLeft(isLeft)
{

}

MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message)
{
    //1.创建对象和布局管理器
    MessageItem* messageItem = new MessageItem(isLeft);
    QGridLayout* layout = new QGridLayout();
    layout->setContentsMargins(20, 10, 40, 0);
    layout->setSpacing(10);

    //messageItem 最低不能小于 100
    messageItem->setMinimumHeight(100);
    messageItem->setLayout(layout);

    //2.创建头像
    QPushButton* avatarBtn = new QPushButton();
    avatarBtn->setFixedSize(40, 40);
    avatarBtn->setIconSize(QSize(40, 40));
    avatarBtn->setIcon(message.sender.avatar);
    avatarBtn->setStyleSheet("QPushButton { border: none;}");
    //根据消息设置位置
    if (isLeft)
    {
        layout->addWidget(avatarBtn, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignLeft);
    }
    else
    {
        layout->addWidget(avatarBtn, 0, 1, 2, 1, Qt::AlignTop | Qt::AlignLeft);
    }

    //3.创建名字和时间
    QLabel* nameLabel = new QLabel();
    nameLabel->setText(message.sender.nickname + " | " + message.time);
    nameLabel->setAlignment(Qt::AlignBottom);
    nameLabel->setStyleSheet("QLabel { font-size: 12px; color: rgb(178, 178, 178);}");
    if (isLeft)
    {
        layout->addWidget(nameLabel, 0, 1);

    }
    else
    {
        layout->addWidget(nameLabel, 0, 0, Qt::AlignRight);
    }

    //4.创建消息体
    QWidget* contentWidget =nullptr;
    switch (message.messageType)
    {
    case TEXT_TYPE:
        contentWidget = makeTextMessageItem(isLeft, message.content);
        break;
    case IMAGE_TYPE:
        contentWidget = makeImageMessageItem(isLeft, message.fileId, message.content);
        break;
    case FILE_TYPE:
        contentWidget = makeFileMessageItem(isLeft, message);
        break;
    case SPEECH_TYPE:
        contentWidget = makeSpeechMessageItem(isLeft, message);
        break;
    default:
        LOG() << "错误的消息类型! messageType=" << message.messageType;
    }
    if (isLeft)
    {
        layout->addWidget(contentWidget, 1, 1);
    }
    else
    {
        layout->addWidget(contentWidget, 1, 0);
    }




    // 5. 连接信号槽处理用户点击头像操作
    connect(avatarBtn, &QPushButton::clicked, messageItem, [=](){
        MainWidget* mainWidget = MainWidget::getInStance();
        UserInfoWidget* userInfoWidget = new UserInfoWidget(message.sender, mainWidget);
        userInfoWidget->exec();
    });

    // 6. 当用户修改昵称时候同步修改此处的用户昵称
    if (!isLeft) {
        //只是针对右侧消息(自己的消息)做下列操作
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::changeNicknameDone, messageItem, [=](){
            nameLabel->setText(dataCenter->getMyself()->nickname + " | " + message.time);
        });

        //头像
        connect(dataCenter, &DataCenter::changeAvatarDone, messageItem, [=](){
            UserInfo* myself = dataCenter->getMyself();
            avatarBtn->setIcon(myself->avatar);
        });
    }


     return messageItem;
}





QWidget *MessageItem::makeTextMessageItem(bool isLeft, const QString &text)
{
    MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft, model::TEXT_TYPE, "", QByteArray());

    return messageContentLabel;
}

QWidget *MessageItem::makeImageMessageItem( bool isLeft, const QString& fileId, const QByteArray& content)
{
    MessageImageLabel* messageImageLabel = new MessageImageLabel(fileId, content, isLeft);
    return messageImageLabel;

}

QWidget *MessageItem::makeFileMessageItem(bool isLeft, const Message& message)
{
    MessageContentLabel* messageContentLabel = new MessageContentLabel("[文件]" + message.fileName, isLeft, message.messageType,\
                                                                       message.fileId, message.content);

    return messageContentLabel;
}

QWidget *MessageItem::makeSpeechMessageItem(bool isLeft, const Message& message)
{
    MessageContentLabel* messageContentLabel = new MessageContentLabel("[语音]", isLeft, message.messageType, message.fileId, message.content);

    return  messageContentLabel;
}


/////////////////////////////
///创建类表示文本消息的正文部分
///也表示文件消息
/////////////////////////////
MessageContentLabel::       MessageContentLabel(const QString& text, bool isLeft, model::MessageType messageType, const QString& fileId,
                                         const QByteArray& content)
    :isLeft(isLeft), messageType(messageType), fileId(fileId), content(content)

{
    //设置下 SizePolicy
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QFont font;
    font.setFamily("微软雅黑");
    font.setPixelSize(16);

    this->label = new QLabel(this);
    this->label->setText(text);
    //qDebug() << text;
    this->label->setFont(font);
    this->label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
    //设置文本自动换行
    this->label->setWordWrap(true);
    //设置内边距,设置行高(文字高度 + 行间距(如果文字高度为16px，行高设为1.2，真实行高为16*1.2=19.2, 行间距为19.2 -16 = 3.2)
    this->label->setStyleSheet(" QLabel { padding: 0 10px; line-height: 1.2; background-color: transparent; }");

    //针对文件消息,并且content 为空的情况下, 通过网络来加载数据
    if (messageType == model::TEXT_TYPE) {
        return;
    }

    if (this->content.isEmpty()) {
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageContentLabel::updateUI);
        dataCenter->getSingleFileAsync(this->fileId);

    } else {
        // content不为空当前数据已经是现成的,直接就把 表示加载状态的变量设为true
        this->loadContentDone = true;
    }


}

//这个函数会在该控件被显示的时候自动调用
void MessageContentLabel::paintEvent(QPaintEvent *event)
{
    (void) event;
    // 1. 获取到父元素的宽度
    QObject* object = this->parent();
    if (!object->isWidgetType())
    {
        //说明当前对象的父元素不是预期的QWidget,此时不需要进行任何绘制操作
        return;
    }

    QWidget* parent = dynamic_cast<QWidget*>(object);
    //总宽度
    int width  = parent->width() * 0.6;

    // 2 . 计算当前文本,如果是一行放置需要多宽
    QFontMetrics metrics(this->label->font());

    //一行文本的宽度
    int totalWidth = metrics.horizontalAdvance(this->label->text());

    //3 . 计算出此处行数是多少,width - 40 留下左右20px的空隙, 至少为一行
    int rows = (totalWidth / (width - 40))  + 1;

    if (rows == 1)
    {
        //如果此时得到的行数只有一行,增加一下宽度,左右各有20px的宽度
        width = totalWidth + 40;
    }

    // 4 .根据行数计算得到高度,+20，上下均有10px的高度
    int height = rows * (this->label->font().pixelSize() * 1.2) + 20;

    // 5 . 绘制圆角矩形和箭头
    QPainter painter(this);
    QPainterPath path;
    //设置抗锯齿,使图像更细腻。
    painter.setRenderHint(QPainter::Antialiasing);

    if (isLeft)
    {
        //线条颜色
        painter.setPen(QPen(QColor(255, 255, 255)));

        //内容填充颜色
        painter.setBrush(QColor(255, 255, 255));

        //绘制矩形，10当前元素相对于父元素的坐标
        painter.drawRoundedRect(10, 0, width, height, 10, 10);

        //绘制箭头
        path.moveTo(10, 15);
        path.lineTo(0, 20);
        path.lineTo(10, 25);

        //使绘制的线成为闭合的多边形
        path.closeSubpath();

        //绘制
        painter.drawPath(path);
        this->label->setGeometry(10, 0, width, height);

    }
    else
    {

        painter.setPen(QPen(QColor(137, 217, 97)));
        painter.setBrush(QColor(137, 217, 97));

        //圆角矩形左上角横坐标MessageContentLabel的宽度-圆角矩形自身的宽度-10(容纳箭头)

        //圆角矩形左侧横坐标位置
        int leftPos = this->width() - width- 10; //10用来容纳箭头的宽度

        //圆角矩形右侧边横坐标位置
        int rightPos = this->width() - 10;

        //绘制圆角矩形
        painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);

        //绘制箭头
        path.moveTo(rightPos, 15);
        path.lineTo(rightPos + 10, 20);
        path.lineTo(rightPos, 25);
        path.closeSubpath();

        //绘制操作
        painter.drawPath(path);

        this->label->setGeometry(leftPos, 0, width, height);

    }

    //6 .重新设置父元素的高度确保父元素足够高,能容纳下上述绘制的消息显示区域
    //注意高度涵盖名字和时间的高度留下冗余空间
    parent->setFixedHeight(height + 50);
}

void MessageContentLabel::updateUI(const QString &fileId, const QByteArray &fileContent)
{
    //也和刚才图片消息的处理一样, 就需要判定收到的数据属于哪个 fileId 的
    if (fileId != this->fileId) {
        return;
    }
    this->content = fileContent;
    this->loadContentDone = true;
    //对于文件消息来说,要在界面上显示"[文件] test.txt"这样的形式, 这个内容和文件content无关
    //在从服务器拿到真正的文件之前,界面内容应该就是绘制好了,此时拿到正文之后,界面应该也不必做出任何实质性的调整
    //所以下列 this->update()，没有也行
    this->update();
}

void MessageContentLabel::mousePressEvent(QMouseEvent *event)
{
    //实现鼠标点击后,触发文件另存为
    if (event->button() == Qt::LeftButton) {
        //左键按下
        if (this->messageType == model::FILE_TYPE) {
            //真正触发另存为
            //万一用户传输的文件就是空文件,更严谨的做法是使用单独的变量判断
            // if (this->content.isEmpty()) {
            //     Toast::showMessage("数据尚未加载成功,请稍后重试");
            //     return;
            // }
            if (!this->loadContentDone) {
                Toast::showMessage("数据尚未加载成功,请稍后重试");
                return;
            }
            saveAsFile(this->content);
        } else if (this->messageType == model::SPEECH_TYPE){
            if (!this->loadContentDone) {
                Toast::showMessage("数据尚未加载成功, 请稍后重试");
                return;
            }
            SoundRecorder* soundRecorder = SoundRecorder::getInstance();
            this->label->setText("播放中...");
            connect(soundRecorder, &SoundRecorder::soundPlayDone, this, &MessageContentLabel::playDone, Qt::UniqueConnection);
            soundRecorder->startPlay(content);






        } else {
            // 其他消息, 比如普通文本消息
            //啥也不做
        }
    }
 }

void MessageContentLabel::saveAsFile(const QByteArray &content)
{
    //弹出对话框,让用户选择路径
    QString filePath = QFileDialog::getSaveFileName(this, "另存为", QDir::homePath(), "*");
    if (filePath.isEmpty()) {
        LOG() << "用户取消了文件另存为";
        return;
    }
    writeByteArrayToFile(filePath, content);

}

void MessageContentLabel::playDone()
{
    if (this->label->text() == "播放中...") {
        this->label->setText("[语音]");
    }
}

void MessageContentLabel::contextMenuEvent(QContextMenuEvent *event)
{
    (void)event;
    if (messageType != model::SPEECH_TYPE) {
        LOG() << "非语音消息暂时不支持右键菜单";
        return;
    }
    //菜单
    QMenu* menu = new QMenu(this);
    QAction* action = menu->addAction("语音转文字");
    connect(action, &QAction::triggered, this, [=](){
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::speechConvertTextDone, this, &MessageContentLabel::speechConvertTextDone, Qt::UniqueConnection);
        dataCenter->speechConvertTextAsync(this->fileId, this->content);

    });

    //此处弹出 "模态对话框"显示菜单/菜单项,exec会在用户进一步操作之前,阻塞
    //在鼠标所处位置弹出菜单
    menu->exec(event->globalPos());
    delete menu;


}

void MessageContentLabel::speechConvertTextDone(const QString &fileId, const QString &text)
{
    if (this->fileId != fileId) {
        //直接跳过,此时结果不是针对这一条语音消息的结果
        return;
    }
    //修改界面内容
    this->label->setText("[语音转文字]" + text);
    this->update();
}





/////////////////////////////
///创建类表示图片消息的正文部分
/////////////////////////////
MessageImageLabel::MessageImageLabel(const QString &fileId, const QByteArray &content, bool isLeft)
    :fileId(fileId)
    ,content(content)
    ,isLeft(isLeft)
{
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    imageBtn = new QPushButton(this);
    imageBtn->setStyleSheet("QPushButton { border: none;} ");

    //针对这个类中的成员有几种情况
    //1.fileId为空,content为图片内容, 本地在给服务器发送图片消息,这个对象就是右侧消息
    //2.fileId不为空, content为空, 收到了服务器推送的消息,从服务器加载消息列表
    //2. 从服务器拿到的图片消息,响应结果里只包含fileId,想要拿到图片的真实数据
    //2.需要再次给服务器发起一个"根据filedId拿到文件内容"这样的请求


    if (content.isEmpty()) {
        //此处这个控件是针对从服务器拿到图片消息这种情况
        //此处拿着fileId, 去服务器获取图片内容
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageImageLabel::updateUI);
        dataCenter->getSingleFileAsync(fileId);

    }


}

void MessageImageLabel::updateUI(const QString& fileId, const QByteArray& content)
{
    if (this->fileId != fileId) {
        //没对上fileId,当前响应的图片是其他图片消息 请求的
        return;
    }

    //对上了,真正显示图片内容
    this->content = content;

    //进行绘制图片到界面上的操作
    this->update();
}

//真正进行绘制图片到界面上的操作
void MessageImageLabel::paintEvent(QPaintEvent *event)
{
    (void)event;
    // 1. 先拿到该元素的父元素,看父元素的宽度是多少
    //此处显示图片宽度的上限, 父元素宽度的60%
    QObject* object = this->parent();
    if (!object->isWidgetType()) {
        //这个逻辑理论上来说是不会存在的
        return;
    }
    QWidget* parent = dynamic_cast<QWidget*>(object);
    int width = parent->width() * 0.6;

    // 2.加载二进制数据为图片对象
    QImage image;
    if (content.isEmpty()) {
        //此时说明图片的响应数据还没回来
        //此处先拿一个固定默认图片顶替一下
        QByteArray tmpContent = loadFileToByteArray(":/resource/image/image.png");
        image.loadFromData(tmpContent);
    } else {
        //此处的 load 操作QImage 能够自动识别当前图片是啥类型的 (png, jpg)
        image.loadFromData(content);
    }

    // 3.针对图片进行缩放
    int height = 0;
    if (image.width() > width) {
        //发现图片更宽,就需要把图片缩放, 使用width作为实际的宽度
        //等比例缩放
        height = ((double)image.height() / image.width()) * width;
    } else {
        //图片本身不太宽,不需要缩放
        height = image.height();
        width = image.width();
    }
    //pixmap 只是一个中间变量 QImage不能直接转成QIcon, 需要QPixmap 中转一下
    QPixmap pixmap = QPixmap::fromImage(image);
    //imageBtn->setFixedSize(width, height);
    imageBtn->setIconSize(QSize(width, height));
    imageBtn->setIcon(QIcon(pixmap));

    // 4. 由于图片高度是计算算出来的, 该元素的父对象的高度能容纳下当前的元素
    //此处加50为了容纳下上方的名字部分,同时留下一点冗余空间
    parent->setFixedHeight(height + 50);

    // 5. 确定按钮所在位置
    //左侧消息和右侧消息显示位置不同
    if (isLeft) {
        imageBtn->setGeometry(10, 0, width, height);
    } else {
        int leftPos = this->width() - width - 10;
        imageBtn->setGeometry(leftPos, 0, width, height);
    }



}
